﻿#pragma once
#include"com.hpp"
#include"pageCache.hpp"

class CentralCache// 单例
{
public:
	static CentralCache* getCentualCache()
	{
		return &_Ccache;
	}

	void BackToSpans(void* begin, void* end, size_t size)//处理threadCache还回来的一段空间(链表)
	{
		//我们想要将这些小空间还给span，那么我们需要找到小空间属于的span
		void* cur = begin;
		size_t bucketPos = CaculateSize::getBucketPos(size);
		_SpanBucket[bucketPos]._mtx.lock();//上锁，接下来要访问桶
		while (cur != nullptr)
		{
			void* next = GetNext(cur);
			Span* span = PageCache::getPageCache()->FindBelongSpan(cur);//找到cur属于的span
			//头插到这个span中
			GetNext(cur) = span->_freeList;
			span->_freeList = cur;

			span->_FetchCount--;
			//如果我们插入时发现我们这个span正好由于这一次插入，所有的小块内存都还回来了,我们就要将这个span交给PageCache处理
			if (span->_FetchCount == 0)
			{
				_SpanBucket[bucketPos].erase(span);//先将这个桶移除出span我们进行解桶锁，然其他线程可以访问桶
				_SpanBucket[bucketPos]._mtx.unlock();

				//再将这个span重置一下,这一步其实不要页没关系
				span->_freeList = nullptr;
				span->_next = nullptr;
				span->_prev = nullptr;

				PageCache::getPageCache()->_mtx.lock();//访问PageCache层，给PageCache层上锁
				PageCache::getPageCache()->BackToPageCache(span);//再将这个span通过函数交给PageCache层处理
				PageCache::getPageCache()->_mtx.unlock();//访问完PageCache层，解锁

				_SpanBucket[bucketPos]._mtx.lock();//上锁，接下来还是要访问桶
			}
			cur = next;
		}
		_SpanBucket[bucketPos]._mtx.unlock();
	}

	Span* getSpan(size_t bucketPos, size_t size)
	{
		//首先看我们的spanbucket中有没有空闲位置，有就直接返回
		Span* it = _SpanBucket[bucketPos].begin();
		if (it != _SpanBucket[bucketPos].end())
		{
			if (it->_freeList != nullptr)
			{
				return it;
			}
			it = it->_next;
		}

		//这里可以先解锁，因为我们已经暂时不需要访问SpanBucket桶了
		_SpanBucket[bucketPos]._mtx.unlock();

		//如果没有空闲位置，就需要调用底层的PageCache来获取
		size_t PageNum = CaculateSize::GetPageOnce(size);//这里使用的算法是用来更好的判断获取多少页的Span合适的

		//因为接下来要去访问PageCache层的空间了，所有要加PageCache层的锁
		PageCache::getPageCache()->_mtx.lock();
		Span* newspan = PageCache::getPageCache()->newSpan(PageNum);
		PageCache::getPageCache()->_mtx.unlock();//访问完解锁

		newspan->_Isuse = true;//设置这个span被使用了
		newspan->_objSize = size;//设置要切分成小块的大小

		//成功获取一个空闲span后，要将这个span划分好然后放到CentralCache的桶中
		char* start = (char*)((newspan->_pageNum) << PAGE_SHIFT);//获取这个页的起始位置
		size_t btye = newspan->_pageCount << PAGE_SHIFT;//总页的大小
		char* end = start + btye;//页的末尾位置后一个字节

		////开始划分这个span，初始化span->_freeList中的内存,连续的小块
		char* tail = start;
		newspan->_freeList = start;
		while (tail <= end  && tail != nullptr)//记住这里tail不能只等于end，一定要小于等于，因为size不一定是整除btye的
		{
			char* next = tail + size;
			if (next >= end)//记住这里next也不能只等于end，一定要大于等于，因为size不一定是整除btye的
			{
				GetNext(tail) = nullptr;//此时tail所在位置已经不足一个小块的大小了，所以让tail这个位置直接置空
				break;
			}
			GetNext(tail) = next;
			tail = next;
		}

		//下面又要修改桶了，所以要加锁
		_SpanBucket[bucketPos]._mtx.lock();

		//划分好后将这个span插入到spanbucket中
		_SpanBucket[bucketPos].PushHead(newspan);
		return newspan;
	}

	size_t FetchFromPageCache(void*& begin, void*& end, size_t fetchSizeOnce, size_t size)// 从底层获取空间,返回值为真正获取的小空间数量
	{
		assert(fetchSizeOnce > 0 && fetchSizeOnce <= 512);
		size_t bucketPos = CaculateSize::getBucketPos(size);
		size_t actualSize = 1;

		_SpanBucket[bucketPos]._mtx.lock();//上桶锁，保证线程安全

		Span* Space = getSpan(bucketPos, size);
		begin = Space->_freeList;
		end = begin;
		//移动需要获取的个数次，如果无法移动这么多次，能移动多少就移动多少次
		for (size_t i = 0;i < fetchSizeOnce - 1;i++)
		{
			if (GetNext(end) == nullptr)
				break;
			end = GetNext(end);
			actualSize++;
		}
		Space->_freeList = GetNext(end);
		GetNext(end) = nullptr;

		Space->_FetchCount += actualSize;

		_SpanBucket[bucketPos]._mtx.unlock();

		return actualSize;
	}

private:
	CentralCache()
	{
	}

	CentralCache(const CentralCache&) = delete;
	CentralCache operator =(const CentralCache&) = delete;

	static CentralCache _Ccache;

	SpanList _SpanBucket[BucketNum];
};

CentralCache CentralCache::_Ccache;
